<?php

/**
 * Celebrio Database module
 * 
 * @copyright  Copyright (c) 2011 Celebrio Software
 * @package    Celebrio
 * @subpackage Database
 */

namespace Celebrio\Database;

use Nette\Object;
use Nette\Environment;

use dibi;
use DibiException;
use DibiDriverException;

use Celebrio\AccessControl\AclConstants;
use Celebrio\AccessControl\AlbireoUser;
use Celebrio\AccessControl\UserManagement;

use Celebrio\Core\CriticalException;
use Celebrio\Core\ModuleInstallationException;
use Celebrio\Core\DummyInit;
use Celebrio\Core\DDL;
use Celebrio\Core\ServiceProvider;

use Celebrio\PasswordHasher;
use Celebrio\ShadowReader;


/**
 * Contains the common functionality of dibi-based Celebrio database
 * modules.
 *
 * @author     pavel
 * @package    Kernel
 */
abstract class DatabaseInit extends DummyInit {

    private $userManagement;

    protected static $configuration;

    public function __construct() {
        $this->userManagement = new UserManagement();
    }

    /**
     * Connects dibi to the database with the given settings.
     * 
     * @param array $settings 
     */
    public function init(array $settings) {

        // we save $settings to the protected static property for later use
        // (DB uninstallation needs this for example)
        self::$configuration = $settings;

        if ($settings == null)
            throw new \Exception("null settings");

        if (dibi::isConnected()) {
            dibi::disconnect();
        }

        $user = Environment::getUser();
        $oldSettings = $settings;
        if ($user->isLoggedIn()) {
            try {
                $dbPassword = PasswordHasher::databaseHash(
                                $user->getIdentity()->getName(),
                                $user->getIdentity()->password
                );

                // try log to DB with user-specific credentials
                $this::setUser($settings, $user->getIdentity()->getName());
                $this::setPassword($settings, $dbPassword);
                //$this::setDatabase($settings, "celebrio");
                //\Nette\Debug::dump($settings);
                dibi::connect($settings);
                return;
            } catch (DibiException $e) {
                // user does not exist in DB or DB is down
                // try log to DB with master credentials several lines below
            }
        }

        try {
            dibi::connect($oldSettings);
        } catch (DibiException $e) {
            // even the master credentials failed, DB is probably down
            throw new CriticalException(_("Kernel is in Critical Mode due to Database system unavailability."), $e->getCode(), $e);
        }
    }

    /**
     * Uninstall database module, which means the following steps:
     *  - database is dropped
     *  - abstract module from config is deleted
     *  - root user is NOT deleted
     */
    public function uninstall() {
        $config = dibi::getConnection()->getConfig();
        $dbName = static::getDatabase($config);

        // we need to connect to "master" database to drop working DB
        static::setMasterDatabase(static::$configuration);
        static::init(static::$configuration);

        // drop database
        dibi::query("DROP DATABASE $dbName");

        // we don't drop root role because when there are 2 databases, they share role
        //dibi::query("DROP ROLE " . AclConstants::ROOT_USER);

        // uninstall abstract DB module (temporary solution)
        // TODO this should be handled by ModuleInstaller itself (abstract modules generally)
        $manifestLoader = new \Celebrio\Core\ManifestLoader();
        $moduleInstaller = new \Celebrio\Core\ModuleInstaller();
        $db_manifest = $manifestLoader->getManifest("Celebrio-Database-abstract");
        $moduleInstaller->uninstall($db_manifest, true);
    }

    /**
     * Performs the installation of database. 
     * 
     * @param array $configuration
     */
    protected function installDatabase(array $configuration) {
        $databaseName = static::getDatabase($configuration);

        // connect to the general database (config user) and create the database
        static::setMasterDatabase($configuration);
        
        
        try {
	        $this->init($configuration);
        	$this->createDB($databaseName); // master u Azure
        } catch(DibiDriverException $dde) {
        	if($dde->getCode()==1007) {
                \Logger::getRootLogger()->error("Can't create database, database already exists");
        		throw new ModuleInstallationException(_("Can't create database, database already exists."));
        	} else {
        		throw $dde;
        	}
        } catch (CriticalException $ce) {
        	if($ce->getCode()==1045) {
                \Logger::getRootLogger()->error("Can't create database, wrong username or password.");
        		throw new ModuleInstallationException(_("Can't create database, wrong username or password."));
        	} else 
        	if($ce->getCode()==2002) {
                \Logger::getRootLogger()->error("Can't create database, cannot reach host.");
        		throw new ModuleInstallationException(_("Can't create database, cannot reach host."));
        	} else {
        		throw $ce;
        	}

        } 
        
        
        dibi::disconnect();

        // connect to the currently created database (config user) and create
        // tables + ROOT_USER login role
        static::setDatabase($configuration, $databaseName);
        $this->init($configuration);
        $this->createTables($databaseName);
        $this->createRoot();

    }

    public function formFields() {
        return array(
            array("name" => "host", "label" => "Host:", "type" => "text", "required" => _('Please enter a host name.')),
            array("name" => "username", "label" => "User:", "type" => "text", "required" => _('Please enter a username.')),
            array("name" => "password", "label" => "Password:", "type" => "password", "required" => _('Please enter a password.')),
            array("name" => "database", "label" => "Database:", "type" => "text", "required" => _('Please enter a database name.')),
        );
    }

    /**
     * Creates database
     *
     * @param array $configuration
     * @param string $databaseName
     */
    private function createDB($databaseName) {

        if (!$this->databaseExists($databaseName)) {
            dibi::query("CREATE DATABASE " . $databaseName);
        } else {
            throw new \Exception("Database already exists.");
        }
    }

    /**
     * Creates database tables
     *
     * @param $dbName
     * @throws \Exception
     */
    private function createTables($dbName) {
        if ($this->tablesExist($dbName)) {
            throw new \Exception("Tables already exist");
        }

        $dbname = ServiceProvider::getServiceName("Celebrio-Database-IDatabase");
        DDL::execute(__DIR__ . "/$dbname/sql/$dbname.sql");
    }

    private function createRoot() {
        $user = new AlbireoUser();
        $user->setUsername(AclConstants::ROOT_USER);
        $user->setLanguage("cs_CZ");
        $user->setTimezone('Europe/Prague');

        $shadowReader = new ShadowReader();
        $celebrity_password = $shadowReader->getPassword(AclConstants::ROOT_USER);

        return $this->userManagement->addUser($user, $celebrity_password);
    }
    
    
    //TODO: temporary solution, rework
    public function getLatestVersion() {
        return "unversioned";
    }
}